import pandas as pd
import numpy as np
from collections import Counter
#任务1
#函数convert_lable用于将数据集标签由文字转化为整数
def convert_lable(text_lable):
    if text_lable == 'Iris-setosa':
        return 1
    elif text_lable == 'Iris-versicolor':
        return 2
    elif text_lable == 'Iris-virginica':
        return 3

#任务2
#函数z_score_scaler用于实现数据集标准化
def z_score_scaler(data:pd.DataFrame)->pd.DataFrame:
    for col in data.columns.drop('Name'):
        mean = data[col].mean()
        std = data[col].std()
        data[col] = (data[col] - mean) / std
    return data
#任务2
#检查并输出标准化后的结果是否合理
def check_zcore(data:pd.DataFrame):
    print('\n\n检查并输出标准化后的结果是否合理:')
    for col in data.columns.drop('Name'):
        mean = data[col].mean()
        max = data[col].max()
        min = data[col].min()
        var = data[col].var()
        print('属性{0}的均值为{1:.3f}, 最大值为{2:.3f}， 最小值为{3:.3f}, 方差为{4:.3f}'.format(col, mean, max, min, var))
#任务3-1随机划分数据集
def random_split(data:pd.DataFrame, class_lable, percent=0.3, random_state=None):
    #获得整个数据集索引
    data_loc = data.index.tolist()
    #获得数据集类别列表
    targets = data[class_lable].unique().tolist()
    train_loc = []
    #为了解决随机采样带来的数据比例与原数据可能不一致的问题，这里采用分层随机抽样
    # 对每一类
    for tar in targets:
        data_tar = data[data[class_lable] == tar]
        #不放回随机抽样固定比例
        train_set = data_tar.sample(n=int((1-percent)*len(data_tar)), random_state=random_state, replace=False)
        #将每一类抽取的样本的索引保存起来
        train_loc.extend(train_set.index.tolist())
    #将整个数据集的索引中不属于训练集索引的作为训练集索引
    test_loc = [i for i in data_loc if i not in train_loc]

    #根据索引重新划分数据集
    train_set = data.iloc[train_loc, :]
    test_set = data.iloc[test_loc, :]

    return train_set, test_set
#任务3-2K折交叉验证
#本函数只是一次划分过程，完整的K折交叉验证见主函数的for循环
#本函数采用分层抽样的K折交叉验证，划分后数据集比例与原数据集一致
def k_flod_split(data:pd.DataFrame, class_lable, fold, K=10):
    #获得整个数据集索引

    data_loc = data.index.tolist()
    #获得各类别名称及其数量
    class_num = Counter(data[class_lable])
    targets = data[class_lable].unique().tolist()
    test_loc = []
    #对于每个类别
    for tar in targets:
        #获得该类总数
        count_tar = class_num[tar]
        begin_loc = round(count_tar / K)
        # 筛选剩下该类别，剔除其他类别
        data_tar = data[data[class_lable] == tar]
        # 按照索引获取该类别测试集合
        test_set = data_tar.iloc[begin_loc * fold:begin_loc*(fold+1), :]
        # 将每一类抽取的样本的索引保存起来
        test_loc.extend(test_set.index.tolist())

    #打乱顺序,为了处理不同类别数据量不同的问题。
    #data = data.sample(frac=1, random_state=random_state)
    #选取[data.shape[0] / k向下取整作为测试集数量
    #num_test = data.shape[0] // k
    #test_data = data[num_test*fold:num_test*(fold+1)]
    #frames = [data[:num_test * fold], data[num_test * (fold + 1):]]
    #train_data = pd.concat(frames)

    train_loc = [i for i in data_loc if i not in test_loc]
    # 根据索引重新划分数据集
    train_set = data.iloc[train_loc, :]
    test_set = data.iloc[test_loc, :]
    return train_set, test_set
#任务4
def rolling_windows(data:pd.DataFrame, wide, step):
    # 错误处理
    if step <= 0 or wide <= 0:
        print('参数有误，请重新输入！')
        return ValueError
    result = pd.DataFrame()
    # 对每一条时间序列
    for seq in data.columns:
        # 暂时存储输出序列
        res = []
        if wide >= len(data):
            res.append(data[seq].mean())
        #初始化窗口位置
        begin = 0
        end = begin + wide
        while end <= len(data):
            #注意，end取不到
            mean = data[seq][begin:end].mean()
            res.append(mean)
            #更新窗口位置
            begin += step
            end = begin + wide
        #将list数据转为Dataframe数据，便于后期合并
        result[seq] = res
    return result
#检验任务4是否正确
def check_task4(data:pd.DataFrame, roll_data:pd.DataFrame, wide, step):
    for seq in data.columns:
        #初始化窗口位置
        begin = 0
        end = begin + wide
        #暂时存储输出序列
        while end <= len(data):
            #将滑动窗口得到的均值和现在计算的均值进行比较，以验证准确性
            mean = data[seq][begin:end].mean()
            if mean != roll_data[seq][int(begin/step)]:
                print('函数计算错误！')
                return
            #更新窗口位置
            begin += step
            end = begin + wide
    return True

if __name__ == '__main__':

    col_names = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'Name']
    skiprows = [0]
    df = pd.read_csv('iris.csv', names=col_names, skiprows=skiprows, converters={'Name': convert_lable})
    '''
    #任务1
    #输出iris数据集前5行
    print('\n\n输出iris数据集前5行\n', df.iloc[:5, ])
    #输出第17行
    print('\n\n输出第17行\n', df.iloc[16, ])
    #输出第2个类别数据的前5行
    print('\n\n输出第2个类别数据的前5行\n', df.iloc[:5, 1])
    '''
    data_orige = df.copy()

    #任务2
    data_zscore = z_score_scaler(df)
    '''
    print('\n\n标准化前原始数据集展示:\n', data_orige.head(5))
    print('\n\n标准化后数据集展示:\n', data_zscore.head(5))
    check_zcore(data_zscore)
    '''

    #任务3-1 将数据集随机划分为训练集和测试集合，比例为7：3
    train_r, test_r = random_split(data=data_zscore, class_lable='Name', percent=0.3, random_state=None)
    print('\n随机划分后训练集数据量：', train_r.shape[0])
    print('\n随机划分后测试集数据量：', test_r.shape[0])

    print('\n随机划分后训练集数据比例', Counter(train_r['Name']))
    print('\n随机划分后测试集数据比例', Counter(test_r['Name']))

    print('\n随机划分后测试集索引', test_r.index.tolist())
    print('\n随机划分后训练集与测试集交集是否为空：', not set(train_r.index.tolist()) & set(test_r.index.tolist()))

    #任务3-2 K折交叉验证划分数据集
    k = 10
    test_k_total = pd.DataFrame()

    #用于测试刻意改变数据比例后十折交叉验证还能否正常工作，将Name==3的数据删除30条，剩余20条，数据集总比例为5：5：2
    data_zscore = data_zscore.drop([i for i in range(120, 150)])
    print(type(data_zscore))

    for fold in range(k):
        train_k, test_K = k_flod_split(data=data_zscore, class_lable='Name', fold=fold, K=k)
        #由于十折划分后数据集太多，为了方便展示，这里只展示了第1轮划分的结果
        print('\n第', fold+1, '轮十折划分后训练集数据量：', train_k.shape[0])
        print('第', fold+1, '轮十折划分后测试集数据量：', test_K.shape[0])

        print('第', fold+1, '轮十折划分后训练集数据比例', Counter(train_k['Name']))
        print('第', fold+1, '轮十折划分后测试集数据比例', Counter(test_K['Name']))

        print('第', fold+1, '轮十折划分测试集索引', test_K.index.tolist())
        print('第', fold+1, '轮十折划分后测后训练集与测试集交集是否为空：', not set(train_k.index.tolist()) & set(test_K.index.tolist()))

        #通过将每一轮的测试机合并，我们得到了一个完整的原数据集，说明实验成功的将原数据集分为10个不相交的子集用于测试。
        test_k_total = pd.concat([test_k_total, test_K])
    print(test_k_total)

    #任务4
    TS_data = pd.read_csv('timeseries_three799.txt', names=['TS1', 'TS2', 'TS3'], delim_whitespace=True)
    wide = 10
    step = 5
    roll = rolling_windows(TS_data, wide=wide, step=step)
    print(roll)
    print(check_task4(data=TS_data, roll_data=roll, wide=wide, step=step))




